home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1993 July / InfoMagic USENET CD-ROM July 1993.ISO / sources / unix / volume15 / ddd < prev    next >
Encoding:
Internet Message Format  |  1988-09-22  |  17.6 KB

  1. Subject:  v15i084:  Fast, multi-process dd(1) clone
  2. Newsgroups: comp.sources.unix
  3. Sender: sources
  4. Approved: rsalz@uunet.UU.NET
  5.  
  6. Submitted-by: Tapani Lindgren <nispa@cs.hut.fi>
  7. Posting-number: Volume 15, Issue 84
  8. Archive-name: ddd
  9.  
  10. Hi!  I wrote a little utility to speed up dumping to tape.
  11. It is a subset of dd(1), but has a much better throughput.
  12. I call it "ddd" for "Douple-speed DD".
  13. I posted it to eunet.sources about a month ago; a bug and
  14. some portability problems were found, but they are corrected
  15. in this second version.
  16. I planned to add some features and port the thing to Minix,
  17. but I won't have time bofere October, so it seems I've better
  18. post it now, as it is, and let others play with it too.
  19. See man page for details.
  20.  
  21. Tapani Lindgren                              | Email <nispa@cs.hut.fi> or
  22. Helsinki University of Technology            | <nispa@finhutcs.BITNET> or
  23. Laboratory of Information Processing Science | <mcvax!santra!sauna!nispa>
  24.  
  25. ------- CUT HERE ------- OUCH! -------
  26. #! /bin/sh
  27. # This is a shell archive.  Remove anything before this line, then unpack
  28. # it by saving it into a file and typing "sh file".  To overwrite existing
  29. # files, type "sh file -c".  You can also feed this as standard input via
  30. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  31. # will see the following message at the end:
  32. #               "End of shell archive."
  33. # Contents:  README ddd.1 Makefile ddd.c
  34. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  35. if test -f README -a "${1}" != "-c" ; then
  36.   echo shar: Will not over-write existing file \"README\"
  37. else
  38. echo shar: Extracting \"README\" \(506 characters\)
  39. sed "s/^X//" >README <<'END_OF_README'
  40. XDDD     DOUBLE-SPEED DATA DUMPER
  41. X
  42. XDdd is a stripped-down enhanced-throughput multi-process dd(1) clone.
  43. X
  44. XThis is version 2.  It has some bugs fixed.  The code is also cleaner
  45. Xand more portable.  Forget about version 1 and destroy all copies of it.
  46. X(version 1 is the set of files _without_ version numbers...)
  47. X
  48. XSee man page and source for details.
  49. X
  50. XFUTURE PROJECETS:
  51. XImplement skip, files, count and seek opitions,
  52. Xas well as noerror and swab conversions.
  53. XSupport for multivolume files - replace bundle(1).
  54. END_OF_README
  55. if test 506 -ne `wc -c <README`; then
  56.     echo shar: \"README\" unpacked with wrong size!
  57.     echo "This is probably space/tabs problems, do not worry"
  58. fi
  59. # end of overwriting check
  60. fi
  61. if test -f ddd.1 -a "${1}" != "-c" ; then
  62.   echo shar: Will not over-write existing file \"ddd.1\"
  63. else
  64. echo shar: Extracting \"ddd.1\" \(2332 characters\)
  65. sed "s/^X//" >ddd.1 <<'END_OF_ddd.1'
  66. X.TH DDD 1L
  67. X.SH NAME
  68. Xddd \- double-speed data dumper
  69. X.SH SYNOPSIS
  70. X.B ddd [option=value] ...
  71. X.SH DESCRIPTION
  72. X.IR Ddd
  73. Xworks almost the same way as dd(1), but it has a much better
  74. Xthroughput, especially when used with slow i/o-devices, such as
  75. Xtape drives.  The improvement is achieved mainly by dividing
  76. Xthe copying process into two processes, one of which reads while
  77. Xthe other one writes and vice versa.  Also all code conversion
  78. Xcapabilities are omitted.  There is no additional overhead copying
  79. Xdata between various conversion buffers.
  80. X
  81. XDdd was inspired by the vast difference in speed between BSD4.2 and
  82. XBSD4.3 dumps - in BSD4.3 dump(8) uses alternating processes to write
  83. Xto raw magnetic tape, thus keeping the tape continuously in motion.
  84. XI wanted to get the same improvement to remote dumps, so this
  85. Xfilter was needed.  Directing all physical I/O through ddd usually
  86. Xincreases the throughput of any pipeline of unix commands
  87. X(if you have enough MIPS and RAM to handle two extra processes).
  88. X.SH OPTIONS
  89. XDdd uses options if, of, ibs and obs exactly as dd(1).  Option bs can
  90. Xalso be used to specify ibs and obs at once.  One option differs slightly
  91. Xin meaning: cbs can be used to specify the size of the internal buffer.
  92. XInput and output processes will swap duties when cbs bytes have been
  93. Xtransferred.  Default values for all sizes are 512 bytes.
  94. XAs with dd(1), letters k (kilobyte), b (block) or w (word) can be
  95. Xappended to size values.
  96. XOther options are not provided.
  97. X.SH HINTS
  98. XFor best performance, block sizes should be rather large.  For magnetic
  99. Xtape, I use obs=100b and cbs=500b or so.  Large block sizes (~100b) are
  100. Xalso effective for network connections.  However, cbs should be small
  101. Xenough for all the data to fit in core, since page faults add
  102. Xoverhead.
  103. X.SH AUTHOR
  104. XTapani Lindgren <nispa@cs.hut.fi>
  105. X.br
  106. XLaboratory of Information Processing Science
  107. X.br
  108. XHelsinki University of Technology
  109. X.br
  110. XFinland
  111. X.SH SEE ALSO
  112. Xdd(1), tar(1), dump(8)
  113. X.SH BUGS
  114. XShould you find one, let me know!
  115. X.SH WARNING
  116. X(Applies to U.S. residents & citizens only)
  117. X.br
  118. XDo not use this program!  Get rid of it as soon as you can!
  119. XIt will probably corrupt all your data, break down your computer
  120. Xand cause severe injury to the operators.
  121. XEven reading the source code may give you a headache.
  122. XI warned you!  I will take no responsibility whatsoever!
  123. END_OF_ddd.1
  124. if test 2332 -ne `wc -c <ddd.1`; then
  125.     echo shar: \"ddd.1\" unpacked with wrong size!
  126.     echo "This is probably space/tabs problems, do not worry"
  127. fi
  128. # end of overwriting check
  129. fi
  130. if test -f Makefile -a "${1}" != "-c" ; then
  131.   echo shar: Will not over-write existing file \"Makefile\"
  132. else
  133. echo shar: Extracting \"Makefile\" \(605 characters\)
  134. sed "s/^X//" >Makefile <<'END_OF_Makefile'
  135. X# Makefile for ddd
  136. X
  137. XDEFS = -DBSD
  138. XCFLAGS = -O $(DEFS)
  139. X
  140. XCC = cc
  141. XLINT = lint
  142. XCP = cp
  143. XRM = /bin/rm -f
  144. X
  145. XSRC = ddd.c
  146. XOBJ = ddd.o
  147. XHEAD =
  148. XBIN = ddd
  149. XSHAR = ddd.shar
  150. X
  151. XBINDIR = /usr/local/bin
  152. X
  153. XMAN = ddd.1
  154. XMANDIR = /usr/local/man
  155. X
  156. Xall: $(BIN) lint
  157. X    touch all
  158. X
  159. X$(BIN): $(OBJ) $(HEAD) Makefile
  160. X    $(CC) $(OBJ) -o $(BIN)
  161. X
  162. Xlint: $(SRC) $(HEAD)
  163. X    $(LINT) $(DEFS) $(SRC)
  164. X    touch lint
  165. X
  166. Xinstall: all
  167. X    strip $(BIN)
  168. X    $(CP) $(BIN) $(BINDIR)
  169. X    $(CP) $(MAN) $(MANDIR)
  170. X
  171. Xclean:
  172. X    -$(RM) $(BIN) $(OBJ) all lint a.out core *~ #* $(SHAR)
  173. X
  174. Xshar: lint README $(MAN) Makefile $(HEAD $(SRC)
  175. X    shar README $(MAN) Makefile $(HEAD) $(SRC) > $(SHAR)
  176. END_OF_Makefile
  177. if test 605 -ne `wc -c <Makefile`; then
  178.     echo shar: \"Makefile\" unpacked with wrong size!
  179.     echo "This is probably space/tabs problems, do not worry"
  180. fi
  181. # end of overwriting check
  182. fi
  183. if test -f ddd.c -a "${1}" != "-c" ; then
  184.   echo shar: Will not over-write existing file \"ddd.c\"
  185. else
  186. echo shar: Extracting \"ddd.c\" \(10706 characters\)
  187. sed "s/^X//" >ddd.c <<'END_OF_ddd.c'
  188. X/*
  189. X * ddd.c - double dd (version 2)
  190. X *
  191. X * Copyright 1988 Helsinki University of Technology.
  192. X * All rights reserved.
  193. X *
  194. X * Permission granted to distribute, use and modify
  195. X * this code for uncommercial purposes, provided
  196. X * that this copyright notice is not changed.
  197. X *
  198. X * Author: Tapani Lindgren (nispa@cs.hut.fi)
  199. X *
  200. X * Ddd is a dd clone that operates as two processes;
  201. X * one process reads while the other one writes and vice versa.
  202. X * This way the throughput may be up to twice as good as that of dd,
  203. X * especially with slow devices such as tape drives.
  204. X *
  205. X * ***** WARNING ***** (For U.S. residents & citizens only)
  206. X *
  207. X * Do not use this program!  Get rid of it as soon as you can!
  208. X * It will probably corrupt all your data, break down your computer
  209. X * and cause severe injury to the operators.
  210. X * Even reading the source code may give you a headache.
  211. X * I warned you!  I will take no responsibility whatsoever!
  212. X */
  213. X
  214. X/* declarations common to all unix versions */
  215. X#include <stdio.h>     /* for fprintf() and stderr() */
  216. X#include <signal.h>    /* for SIGTERM */
  217. Xextern char *malloc();
  218. X
  219. X/* version dependent declarations */
  220. X
  221. X#ifdef BSD
  222. X#include <sys/wait.h>  /* for union wait */
  223. X#include <sys/file.h>  /* for O_RDONLY and O_WRONLY */
  224. Xextern char *sprintf();
  225. X#endif
  226. X
  227. X#ifdef SYSV
  228. X#include <fcntl.h>     /* for O_RDONLY and O_WRONLY */
  229. Xvoid exit();
  230. Xvoid perror();
  231. X#endif
  232. X
  233. X
  234. X
  235. X/* macros to find min or max of two values */
  236. X#define min(a,b) ((a)<(b)? (a): (b))
  237. X#define max(a,b) ((a)>(b)? (a): (b))
  238. X
  239. X/* inherited file descriptors */
  240. X#define STDIN 0
  241. X#define STDOUT 1
  242. X
  243. X/* boolean values */
  244. X#define FALSE 0
  245. X#define TRUE 1
  246. X
  247. X/* pipes have a read end and a write end */
  248. X#define P_REND 0
  249. X#define P_WEND 1
  250. X
  251. X/* there are two pipes; one for read tokens and one for write tokens */
  252. X#define RTOK_P 0
  253. X#define WTOK_P 1
  254. X
  255. X/* token bytes passed along pipes */
  256. X#define TOK_CONT 0     /* go ahead */
  257. X#define TOK_DONE 1     /* end of data */
  258. X#define TOK_ERROR 2    /* something's wrong, you've better stop now */
  259. X
  260. X/* input/output full/short record counters are in a table;
  261. X indexes defined below */
  262. X#define FULLIN 0
  263. X#define SHORTIN 1
  264. X#define FULLOUT 2
  265. X#define SHORTOUT 3
  266. X
  267. X/* defaults */
  268. X#define DEFBS 512      /* default block size */
  269. X
  270. X/* forward declarations */
  271. Xint doerror();
  272. X
  273. X/* global variables */
  274. Xstatic int
  275. X  ifd, ofd,    /* input/output file descriptors */
  276. X  ibs, obs,    /* input/output block sizes */
  277. X  cbs, /* "conversion" buffer size */
  278. X  pid, /* pid of child (in parent) or 0 (in child) */
  279. X  eof = FALSE, /* have we encountered end-of-file */
  280. X  pipefd[2][2],        /* read/write fd's for 2 pipes */
  281. X  counters[4] = {0, 0, 0, 0},  /* input/output full/short record counters */
  282. X  buflen;      /* count of characters read into buffer */
  283. Xstatic char
  284. X  *buffer;     /* address of buffer */
  285. X
  286. X
  287. Xmain(argc, argv)
  288. Xint argc;
  289. Xchar *argv[];
  290. X{
  291. X  (void) catchsignals();       /* prepare for interrupts etc */
  292. X  (void) setdefaults();        /* set default values for parameters */
  293. X  (void) parsearguments(argc, argv);   /* parse arguments */
  294. X  (void) initbuffer(); /* initialize buffer */
  295. X  (void) inittokens(); /* create one READ and one WRITE token */
  296. X  (void) dofork();     /* 1 will be 2 */
  297. X  while (!eof) {       /* enter main loop */
  298. X    (void) gettoken(RTOK_P);   /* compete for first/next read turn */
  299. X    (void) readbuffer();       /* fill buffer with input */
  300. X    (void) gettoken(WTOK_P);   /* make sure we also get the next write turn */
  301. X    /* now others may read if they wish (and if there's any data left */
  302. X    (void) passtoken(RTOK_P, eof? TOK_DONE: TOK_CONT);
  303. X    (void) writebuffer();      /* ... while we write to output */
  304. X    /* this cycle is done now */
  305. X    if (!eof) (void) passtoken(WTOK_P, TOK_CONT);
  306. X  }    /* end of main loop */
  307. X  (void) passcounters(RTOK_P); /* send record counters to our partner */
  308. X  (void) terminate(0); /* and exit (no errors) */
  309. X  /* NOTREACHED */
  310. X}      /* end of main() */
  311. X
  312. X
  313. Xcatchsignals()
  314. X/* arrange for some signals to be catched, so that statistics can be printed */
  315. X{
  316. X  static int siglist[] = {
  317. X    SIGINT, SIGQUIT, SIGILL, SIGFPE,
  318. X    SIGBUS, SIGSEGV, SIGSYS, SIGPIPE,
  319. X    SIGALRM, SIGTERM, 0
  320. X    };
  321. X  int *sigp;
  322. X
  323. X  for (sigp = siglist; *sigp != 0; sigp++)
  324. X    (void) signal(*sigp, doerror);
  325. X}      /* end of catchsignals() */
  326. X
  327. Xdoerror()
  328. X/* what we do if we get an error or catch a signal */
  329. X{
  330. X  /* send error token to both pipes */
  331. X  (void) passtoken(RTOK_P, TOK_ERROR);
  332. X  (void) passtoken(WTOK_P, TOK_ERROR);
  333. X  /* also send i/o record counters */
  334. X  (void) passcounters(RTOK_P);
  335. X  (void) passcounters(RTOK_P);
  336. X  /* terminate with error status */
  337. X  (void) terminate(1);
  338. X}
  339. X
  340. Xterminate(stat)
  341. Xint stat;
  342. X{
  343. X  /* parent will try to wait for child */
  344. X#ifdef BSD
  345. X  if (pid) (void) wait((union wait *) 0);
  346. X#endif
  347. X#ifdef SYSV
  348. X  if (pid) (void) wait((int *) 0);
  349. X#endif
  350. X
  351. X  exit(stat);
  352. X}
  353. X
  354. Xsetdefaults()
  355. X/* set default values */
  356. X{
  357. X  ifd = STDIN;
  358. X  ofd = STDOUT;
  359. X  ibs = obs = DEFBS;   /* block sizes */
  360. X  cbs = 0;     /* initially; will be set to max(ibs, obs, cbs) */
  361. X}
  362. X
  363. Xparsearguments(argc, argv)
  364. Xint argc;
  365. Xchar *argv[];
  366. X  /* parse arguments */
  367. X{
  368. X  /* constant strings "array" for recognizing options */
  369. X  static struct {
  370. X    char *IF, *OF, *CBS, *IBS, *OBS, *BS, *NOTHING;
  371. X  } consts = {
  372. X    "if=", "of=", "cbs=", "ibs=", "obs=", "bs=", ""
  373. X    };
  374. X  char **constp;       /* const structure pointer */
  375. X
  376. X  for (argc--, argv++; argc > 0; argc--, argv++) {
  377. X    constp = (char **) &consts;
  378. X    while (**constp && strncmp(*argv, *constp, strlen(*constp)))
  379. X      constp++;
  380. X    /* constp now points to one of the pointers in consts structure */
  381. X    *argv += strlen(*constp);  /* skip the constant part of the argument */
  382. X    if (constp == &consts.IF) {        /* open another file for input */
  383. X      ifd = open(*argv, O_RDONLY);
  384. X      if (ifd < 0) perror (*argv);
  385. X    } else if (constp == &consts.OF) {
  386. X      ofd = open(*argv, O_WRONLY | O_CREAT);   /* open file for output */
  387. X      if (ofd < 0) perror (*argv);
  388. X    } else if (constp == &consts.CBS) {        /* set buffer size */
  389. X      cbs = evalopt(*argv);
  390. X    } else if (constp == &consts.IBS) {        /* set input block size */
  391. X      ibs = evalopt(*argv);
  392. X    } else if (constp == &consts.OBS) {        /* set output block size */
  393. X      obs = evalopt(*argv);
  394. X    } else if (constp == &consts.BS) { /* set input and output block sizes */
  395. X      ibs = obs = evalopt(*argv);
  396. X    } else {
  397. X      (void) fprintf(stderr,
  398. X                    "usage: ddd [if=name] [of=name] [bs=n] [ibs=n obs=n]\n");
  399. X      exit(1);
  400. X    }
  401. X  } /* end of for loop */
  402. X} /* end of parsearguments() */
  403. X
  404. Xevalopt(p) /* return numerical value of string */
  405. Xchar *p;
  406. X{
  407. X  int temp = 0;
  408. X
  409. X  for ( ; *p >= '0' && *p <= '9'; p++)
  410. X    temp = temp * 10 + *p - '0';
  411. X  if (temp < 1) {
  412. X    (void) fprintf(stderr, "ddd: illegal size option\n");
  413. X    exit(1);
  414. X  }
  415. X  switch (*p) {
  416. X  case '\0':
  417. X    return(temp);
  418. X  case 'w':
  419. X  case 'W':
  420. X    return(temp << 2); /* 4-byte words */
  421. X  case 'b':
  422. X  case 'B':
  423. X    return(temp << 9); /* 512-byte blocks */
  424. X  case 'k':
  425. X  case 'K':
  426. X    return(temp << 10);        /* kilobytes */
  427. X  default:
  428. X    (void) fprintf(stderr, "ddd: bad size option\n");
  429. X    exit(1);
  430. X  }
  431. X  /* NOTREACHED */
  432. X}      /* end of evalopt() */
  433. X
  434. Xinitbuffer()
  435. X/* initialize buffer */
  436. X{
  437. X  cbs = max(cbs, max(ibs, obs));       /* determine buffer size */
  438. X  if (cbs % ibs || cbs % obs) {
  439. X    (void) fprintf(stderr, "ddd: warning: incompatible block/buffer sizes\n");
  440. X  }
  441. X  buffer = malloc((unsigned) cbs);
  442. X  if (buffer == NULL) {
  443. X    (void) perror("ddd: cannot allocate buffer");
  444. X    exit(1);
  445. X  }
  446. X}      /* end of initbuffer() */
  447. X
  448. Xinittokens()
  449. X/* initialize token passing system with 2 pipes */
  450. X{
  451. X  if(pipe(pipefd[RTOK_P]) < 0 || pipe(pipefd[WTOK_P]) < 0) {
  452. X    (void) perror("ddd: cannot create token pipes");
  453. X    exit(1);
  454. X  }
  455. X  /* create initial tokens */
  456. X  (void) passtoken(RTOK_P, TOK_CONT);
  457. X  (void) passtoken(WTOK_P, TOK_CONT);
  458. X}      /* end of inittokens() */
  459. X
  460. Xpasstoken(pipenum, token)
  461. Xint pipenum;
  462. Xchar token;
  463. X/* pass a token to a pipe */
  464. X{
  465. X  if (write(pipefd[pipenum][P_WEND], &token, 1) < 1) {
  466. X    (void) perror("ddd: cannot write token to pipe");
  467. X    exit(1);
  468. X  }
  469. X}      /* end of passtoken() */
  470. X
  471. Xgettoken(pipenum)
  472. Xint pipenum;
  473. X/* wait to read a token from the pipe; also see if we should stop */
  474. X{
  475. X  char tokenbuf;
  476. X
  477. X  if (read(pipefd[pipenum][P_REND], &tokenbuf, 1) < 1) {
  478. X    (void) perror("ddd: cannot read token from pipe");
  479. X    exit(1);
  480. X  }
  481. X  if (tokenbuf != TOK_CONT) {  /* we did not get what we wanted */
  482. X    (void) getcounters(pipenum);       /* report record counters */
  483. X    terminate(tokenbuf == TOK_DONE);   /* TOK_DONE means no error */
  484. X  }
  485. X}      /* end of gettoken() */
  486. X
  487. Xpasscounters(pipenum)
  488. Xint pipenum;
  489. X/* pass read/write counters to the other process */
  490. X{
  491. X  if (write(pipefd[pipenum][P_WEND], (char *) counters,
  492. X           sizeof(counters)) < sizeof(counters)) {
  493. X    (void) perror("ddd: cannot write counters to pipe");
  494. X    exit(1);
  495. X  }
  496. X}
  497. X
  498. Xgetcounters(pipenum)
  499. Xint pipenum;
  500. X/* report input/output record counts */
  501. X{
  502. X  int hiscounters[4];
  503. X
  504. X  if (read(pipefd[pipenum][P_REND], (char *) hiscounters,
  505. X          sizeof(hiscounters)) < sizeof(hiscounters)) {
  506. X    (void) perror("ddd: cannot read counters from pipe");
  507. X    exit(1);
  508. X  }
  509. X  (void) fprintf(stderr,
  510. X                "%d+%d records in\n%d+%d records out\n",
  511. X                counters[FULLIN] + hiscounters[FULLIN],
  512. X                counters[SHORTIN] + hiscounters[SHORTIN],
  513. X                counters[FULLOUT] + hiscounters[FULLOUT],
  514. X                counters[SHORTOUT] + hiscounters[SHORTOUT]
  515. X                );
  516. X}      /* end of printcounters() */
  517. X
  518. Xdofork()
  519. X/* fork into 2 processes */
  520. X{
  521. X  if ((pid = fork()) < 0) {
  522. X    (void) perror("ddd: warning: cannot fork");
  523. X    /* But continue and do our job anyway, as regular dd */
  524. X  }
  525. X}
  526. X
  527. Xreadbuffer()
  528. X/* read buffer from input */
  529. X{
  530. X  int iolen, ioresult;
  531. X
  532. X  buflen = 0;
  533. X  while (buflen < cbs && !eof) {
  534. X    iolen = min(ibs, cbs - buflen);
  535. X#ifdef BSD
  536. X    ioresult = read(ifd, &buffer[buflen], iolen);
  537. X#endif
  538. X#ifdef SYSV
  539. X    ioresult = read(ifd, &buffer[buflen], (unsigned) iolen);
  540. X#endif
  541. X    if (ioresult == 0) {       /* end of file */
  542. X      eof = TRUE;
  543. X    } else if (ioresult < 0) {
  544. X      (void) perror("ddd: read error");
  545. X      (void) doerror();
  546. X    }
  547. X    buflen += ioresult;        /* update current count of chars in buffer */
  548. X    /* if we got any data, update appropriate input record count */
  549. X    if (ioresult > 0) counters[(ioresult == ibs)? FULLIN: SHORTIN]++;
  550. X  }
  551. X}      /* end of readbuffer() */
  552. X
  553. Xwritebuffer()
  554. X/* writing phase */
  555. X{
  556. X  int ocount, iolen, ioresult;
  557. X
  558. X  ocount = 0;  /* count of chars written */
  559. X  while (ocount < buflen) {
  560. X    iolen = min(obs, buflen - ocount);
  561. X#ifdef BSD
  562. X    ioresult = write(ofd, &buffer[ocount], iolen);
  563. X#endif
  564. X#ifdef SYSV
  565. X    ioresult = write(ofd, &buffer[ocount], (unsigned) iolen);
  566. X#endif
  567. X    if (ioresult < iolen) {
  568. X      perror("ddd: write error");
  569. X      (void) doerror();
  570. X    }
  571. X    ocount += ioresult;
  572. X    /* count output records */
  573. X    counters[(ioresult == obs)? FULLOUT: SHORTOUT]++;
  574. X  }
  575. X}      /* end of writebuffer() */
  576. END_OF_ddd.c
  577. if test 10706 -ne `wc -c <ddd.c`; then
  578.     echo shar: \"ddd.c\" unpacked with wrong size!
  579.     echo "This is probably space/tabs problems, do not worry"
  580. fi
  581. # end of overwriting check
  582. fi
  583. echo shar: End of shell archive.
  584. exit 0
  585.  
  586.